/*
Copyright (C) 2016  Marien Raat

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <SFML/Graphics.hpp>
#include <SFML/Network.hpp>
#include <Box2D/Box2D.h>

#include <iostream>

#include "car.h"
#include "field.h"
#include "ball.h"
#include "aiManager.h"
#include "config.h"
#include "networkDefinitions.h"

void initializeGame();
void initializeNetwork(int argc, char **argv);

void updateGame();
void updateNetwork();
sf::Socket::Status receiveNewMessage();

void addPlayer(int player);
void sendUpdatePacket(sf::IpAddress ipAddress, unsigned short port);

b2World world(b2Vec2(0, 0));
float box2DTimeStep = 1.0f / 144.0f;
int velocityIterations = 8
    , positionIterations = 3;

float timeStep = box2DTimeStep;
bool client = false;

int maxCars = 6;
Car *playerCars;
bool *playersConnected;
Field field;
Ball ball;
AIManager aiManager;

sf::UdpSocket socket;
sf::Socket::Status currentSocketStatus;
sf::Packet currentPacket;
sf::IpAddress senderIpAddress;
unsigned short senderPort;

int main(int argc, char **argv)
{
    initializeGame();
    initializeNetwork(argc, argv);

    sf::Clock timer;
    int frames = 0;

    sf::Clock sleepTimer;

    while (true) {
        if (timer.getElapsedTime().asSeconds() > 1.0) {
            std::cout << "\rFPS: " << frames << " " << std::flush;
            timer.restart();
            frames = 0;
        }
        updateNetwork();
        updateGame();
        frames++;
        while (sleepTimer.getElapsedTime().asSeconds() < box2DTimeStep);
        sleepTimer.restart();
    }

    return 0;
}

void initializeGame()
{
    playerCars = new Car[maxCars];
    playersConnected = new bool[maxCars];
    for (int i = 0; i < maxCars; i++) {
        playerCars[i].initialize(&world);
        playersConnected[i] = false;
    }
    field.initialize(&world, FIELD_SIZE);
    ball.initialize(&world, BALL_RADIUS);
    ball.setPosition(0.5f * FIELD_SIZE);
    aiManager.initialize(playerCars, playersConnected, maxCars,
                         &ball);
}

void initializeNetwork(int argc, char **argv)
{
    unsigned short port = sf::Socket::AnyPort;
    if (argc > 1) {
        port = atoi(argv[1]);
    }

    if (socket.bind(port) != sf::Socket::Done) {
        std::cout << "Unable to bind the socket to a port"
                  << std::endl;
        exit(-1);
    }

    std::cout << "Bound the socket to port " << socket.getLocalPort()
              << " succesfully." << std::endl;

    socket.setBlocking(false);

    receiveNewMessage();
}

void updateGame()
{
    ball.update();
    for (int i = 0; i < maxCars; i++)
        playerCars[i].update();
    aiManager.update();

    world.Step(box2DTimeStep, velocityIterations, positionIterations);
    world.ClearForces();
}

void updateNetwork()
{
    while (receiveNewMessage() == sf::Socket::Done) {
        sf::Packet packet = currentPacket;
        sf::IpAddress ipAddress = senderIpAddress;
        unsigned short port = senderPort;

        sf::Int32 packetTypeInt;
        packet >> packetTypeInt;
        PacketType packetType = (PacketType) packetTypeInt;

        if (packetType == PacketJoin) {
            sf::Int32 assignedPlayer;
            for (int i = 0; i < maxCars; i++)
                if (!playersConnected[i]) {
                    assignedPlayer = i;
                    playersConnected[i] = true;
                    break;
                }

            sf::Packet assignPacket;
            sf::Int32 sendPacketType = PacketAssign;
            assignPacket << sendPacketType << assignedPlayer;
            socket.send(assignPacket, ipAddress, port);

            addPlayer(assignedPlayer);

            sendUpdatePacket(ipAddress, port);
        } else if (packetType == PacketUpdate) {
            sf::Int32 player;
            packet >> player;
            float power, turning;
            bool boosting, drifting;
            int dashDirection;
            packet >> power >> turning >> boosting >> drifting
                   >> dashDirection;
            playerCars[player].power = power;
            playerCars[player].turning = turning;
            playerCars[player].boosting = boosting;
            playerCars[player].drifting = drifting;
            playerCars[player].dashDirection = dashDirection;

            sendUpdatePacket(ipAddress, port);
        } else if (packetType == PacketLeave) {
            sf::Int32 player;
            packet >> player;
            playerCars[player].setInWorld(false);
            playersConnected[player] = false;
        }
    }
}

sf::Socket::Status receiveNewMessage()
{
    return
        socket.receive(currentPacket, senderIpAddress, senderPort);
}

void addPlayer(int player)
{
    playerCars[player].setInWorld(true);
    playerCars[player].setPosition(
        sf::Vector2f((player % 2 ? FIELD_SIZE.x - 50 : 50), FIELD_SIZE.y / 2));
    if (player % 2) playerCars[player].setRotation(180);
}

void sendUpdatePacket(sf::IpAddress ipAddress, unsigned short port)
{
    sf::Packet packet;
    packet << ((sf::Int32) PacketUpdate);
    for (int i = 0; i < maxCars; i++) {
        packet << playerCars[i] << playersConnected[i];
    }
    packet << ball;

    socket.send(packet, ipAddress, port);
}
